// wtkwidget.cpp
//

#include "config.h"
#include "wtk/wtkwidget.hpp"
#include "wtk/wtkdisplay.hpp"
#include "wtk/wtklayout.hpp"

using namespace wtk;



#define RESIZE_TRACE(msg...) WTK_TRACE(msg)



// Widget::Widget
//
Widget::Widget ( Widget *parent )
{
  // WTK_TRACE("Widget(%s)", WGL_DEBUG_CNAME(this));
  
  if (parent)
    {
      parent->add_child(*this);
    }
}



// Widget::Widget
//
Widget::Widget ( Display &display )
  : Widget(display.get_root_widget())
{
}



// Widget::~Widget
//
Widget::~Widget ()
{
  // WTK_TRACE("~Widget(%s)", WGL_DEBUG_CNAME(this));
  destroy();
}



// destroy
//
void Widget::destroy ()
{
  if (is_destroyed())
    return;

  WTK_TRACE("Widget.destroy(%s)", WGL_DEBUG_CNAME(this));
  
  reset_layout();

  while (!children.empty())
    (*children.begin())->destroy();
  
  if (parent)
    parent->remove_child(*this);
  
  flags.set(WidgetFlagsBit::bDestroyed);
}



// Widget::add_child
//
void Widget::add_child ( Widget &child )
{
  WTK_ASSERT(!child.parent);

  WTK_TRACE("add_child: %s -> %s", WGL_DEBUG_CNAME(this), WGL_DEBUG_CNAME(&child));
  
  child.parent = this;
  children.push_back(&child);

  if (layout)
    {
      layout->add_child(child);
    }

  child.queue_resize();
}



// Widget::remove_child
//
void Widget::remove_child ( Widget &child )
{
  auto it = std::find(children.begin(),
                      children.end(),
                      &child);

  WTK_ASSERT(it != children.end());

  child.parent = nullptr;
  children.erase(it);

  if (layout)
    {
      layout->remove_child(child);
    }
  
  queue_resize();
}



// Widget::traverse
//
void Widget::traverse ( TraverseFunc func )
{
  real_traverse(func);
}



// Widget::real_traverse
//
Widget::TraverseValue Widget::real_traverse ( TraverseFunc func )
{
  switch (func(*this))
    {
    case TraverseValue::vSkip:
      return TraverseValue::vContinue;
    case TraverseValue::vStop:
      return TraverseValue::vStop;
    case TraverseValue::vContinue:
      break;
    default:
      WTK_FATAL("wrong traverse value!");
    }
  
  for (auto child: children)
    {
      if (child->real_traverse(func) == TraverseValue::vStop)
        return TraverseValue::vStop;
    }

  return TraverseValue::vContinue;
}



// Widget::show_all
//
void Widget::show_all ()
{
  WTK_TRACE("show_all(%s)", WGL_DEBUG_CNAME(this));
  traverse([] ( Widget &widget )
  {
    widget.flags.set(WidgetFlagsBit::bVisible);
    widget.flags.set(WidgetFlagsBit::bNeedsResize);
    return TraverseValue::vContinue;
  });
  queue_resize();
}



// Widget::show
//
void Widget::show ()
{
  if (!is_visible())
    {
      flags.set(WidgetFlagsBit::bVisible);
      queue_resize();
    }
}



// Widget::hide
//
void Widget::hide ()
{
  flags.reset(WidgetFlagsBit::bVisible);
  if (parent)
    parent->queue_resize();
}



// Widget::queue_resize
//
void Widget::queue_resize ()
{
  for (Widget *w = this; w; w = w->parent)
    {
      // WTK_TRACE("queue_resize: %s", WGL_DEBUG_CNAME(w));
      w->flags.set(WidgetFlagsBit::bNeedsResize);
    }
}



// Widget::set_layout
//
void Widget::set_layout ( Layout &layout,
                          bool static_)
{
  // WTK_TRACE("Widget.set_layout(%s, %s)",
  //           WGL_DEBUG_CNAME(this),
  //           WGL_DEBUG_CNAME(&layout));
            
  if (this->layout)
    reset_layout();
  this->layout = &layout;
  flags.set(WidgetFlagsBit::bStaticLayout, static_);

  layout.clear();
  for (auto child: children)
    layout.add_child(*child);
}



// Widget::reset_layout
//
void Widget::reset_layout ()
{
  if (layout)
    {
      // WTK_TRACE("Widget.reset_layout(%s, %s)",
      //           WGL_DEBUG_CNAME(this),
      //           WGL_DEBUG_CNAME(layout));
            
      layout->clear();
      if (!flags.test(WidgetFlagsBit::bStaticLayout))
        delete layout;
    }
  else
    {
      // WTK_TRACE("Widget.reset_layout(%s, nullptr)",
      //           WGL_DEBUG_CNAME(this));
    }
  
  layout = nullptr;
}



// Widget::set_focus
//
void Widget::set_focus ()
{
  Display *display = get_display();
  if (display)
    display->set_focus_widget(this);
}



// Widget::size_request
//
void Widget::size_request ( SizeRequest &req )
{
  RESIZE_TRACE("size_request(%s)", WGL_DEBUG_CNAME(this));
  if (needs_resize())
    {
      req = {0, 0};
      on_size_request(req);
      if (layout)
        {
          layout->size_request(req);
        }
      else
        {
          if (!children.empty())
            WTK_WARNING("widget has children but no layout: %s", WGL_DEBUG_CNAME(this));
        }
      request = req;
      flags.reset(WidgetFlagsBit::bNeedsResize);
    }
  else
    {
      req = request;
    }
  RESIZE_TRACE("size_request(%s) -> {%d, %d}", WGL_DEBUG_CNAME(this), req.width, req.height);
}



// Widget::size_allocate
//
void Widget::size_allocate ( const Allocation &alloc )
{
  RESIZE_TRACE("size_allocate(%s, %d, %d, %d, %d)",
               WGL_DEBUG_CNAME(this),
               alloc.x,
               alloc.y,
               alloc.width,
               alloc.height);
  //
  x = alloc.x;
  y = alloc.y;
  width = alloc.width;
  height = alloc.height;
  //
  if (layout)
    {
      layout->size_allocate(alloc);
    }
  on_size_allocate(alloc);
}



// Widget::event
//
void Widget::event ( const Event &event )
{
  switch (event.type)
    {
    case EventType::KeyDown:
    case EventType::KeyUp:
      event_keyboard(event);
      break;

    case EventType::MouseMotion:
    case EventType::MouseButtonDown:
    case EventType::MouseButtonUp:
    case EventType::MouseWheel:
      event_mouse(event);
      break;
      
    default:
      WTK_TRACE("[TODO]: %u", static_cast<std::underlying_type<EventType>::type>(event.type));
    }
}



// Widget::event_keyboard
//
void Widget::event_keyboard ( const Event &event )
{
  WTK_TRACE("key: %u", event.keyboard.scancode);
}



// Widget::event_mouse
//
void Widget::event_mouse ( const Event &event )
{
  // WTK_TRACE("mouse: %s, %d, %d", WGL_DEBUG_CNAME(this), event.motion.x, event.motion.y);
}



// Widget::draw_child
//
void Widget::draw_child ( const DrawContext &context )
{
  // WTK_TRACE("Widget.draw(%s, %d, %d, %d, %d)",
  //           WGL_DEBUG_CNAME(this),
  //           context.root_x,
  //           context.root_y,
  //           context.clip_width,
  //           context.clip_height);

  // [TEMP] reset context - remove me!
  gl.UseProgram(0);
  gl.Disable(GL_DEPTH_TEST);
  gl.BindTexture(GL_TEXTURE_2D, 0);
  gl.Disable(GL_TEXTURE_2D);
  gl.PolygonMode(GL_FRONT_AND_BACK, GL_FILL);

  // begin draw
  gl.Viewport(context.root_x,
              context.viewport_height - context.clip_height - context.root_y,
              context.clip_width,
              context.clip_height);
  gl.MatrixMode(GL_PROJECTION);
  gl.LoadIdentity();
  
  // gl.Ortho(GLdouble(-context.root_x), GLdouble(context.viewport_width - context.root_x),
  //          GLdouble(context.viewport_height - context.root_y), GLdouble(-context.root_y),
  //          -1.0, 1.0);

  gl.Ortho(0.f,
           GLfloat(context.clip_width),
           0.f,
           GLfloat(context.clip_height),
           -1.0,
           1.0);

  gl.MatrixMode(GL_MODELVIEW);
  gl.LoadIdentity();

  // [FIXME] temp
  float x1 = 1.0f;
  float y1 = 1.0f;
  float x2 = float(x1 + width - 2);
  float y2 = float(y1 + height - 2);

  gl.Begin(GL_TRIANGLES);
  
  gl.Color3f(0.9f, 0.9f, 0.9f);
  gl.Vertex2f(x1, y1);
  gl.Vertex2f(x2, y1);
  gl.Vertex2f(x1, y2);

  gl.Color3f(0.8f, 0.8f, 0.8f);
  gl.Vertex2f(x2, y1);
  gl.Vertex2f(x2, y2);
  gl.Vertex2f(x1, y2);
  
  gl.End();
  
  draw(context);

  for (auto child: children)
    {
      DrawContext child_context{context,
        child->x,
        child->y,
        child->width,
        child->height};
      child->draw_child(child_context);
    }
}



// Widget::get_child_at
//
Widget *Widget::get_child_at ( int x,
                               int y,
                               int *rx,
                               int *ry )
{
  if (x < 0 || x >= width || y < 0 || y >= height)
    return nullptr;

  auto child_end = rend();
  for (auto child = rbegin(); child != child_end; ++child)
    {
      Widget *w = (*child)->get_child_at(x - (*child)->x,
                                         y - (*child)->y,
                                         rx,
                                         ry);
      if (w)
        return w;
    }

  *rx = x;
  *ry = y;
  return this;
}
